﻿using System.Runtime.CompilerServices;
// ReSharper disable SuggestBaseTypeForParameterInConstructor

namespace KinonekoSoftware.CodariaDB.Runtimes
{
    public abstract class ObjectPool : Disposable, IObjectPool
    {
        private readonly ILiteCollection<BsonDocument>     _objs;
        private readonly IDictionary<Type, EmbeddedObject> _cache;

        protected ObjectPool(LiteDatabase database)
        {
            _cache = new Dictionary<Type, EmbeddedObject>();
            _objs  = database.GetCollection<BsonDocument>(CollectionNames.ObjectDB);

            Cache = new ReadOnlyDictionary<Type, EmbeddedObject>(_cache);
        }

        protected override void ReleaseManagedResources()
        {
            base.ReleaseManagedResources();
            
            //
            //
            _cache.Clear();
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private bool HasTypeInDatabase(string typeName) => _objs.HasID(typeName);

        public bool Has<T>() where T : EmbeddedObject => Has(typeof(T));

        public bool Has(Type type)
        {
            var key = type.FullName;
            return _objs.HasID(key);
        }

        public T Set<T>(T instance, bool insert = false) where T : EmbeddedObject
        {
            var key     = typeof(T);
            var keyName = key.FullName;
            
            if (HasTypeInDatabase(keyName) && insert)
            {
                throw new InvalidOperationException();
            }
            
            _cache[key] = instance;
            
            //
            //
            var document = DatabaseBsonMapper.Override
                                     .Serialize(instance)
                                     .AsDocument;
            //
            //
            document[Codaria.ID] = keyName;
            
            //
            //
            if (insert)
            {
                _objs.Insert(document);
            }
            else
            {
                _objs.Update(document);
            }
            
            return instance;
        }

        public T GetOrCreate<T>(Factory<T> factory, bool cache = true) where T : EmbeddedObject
        {

            var key     = typeof(T);
            var keyName = key.FullName;
            T   instance;
            
            if (cache && _cache.TryGetValue(key, out var value))
            {
                return (T)value;
            }
            
            var document = _objs.FindById(keyName);
            
            //
            //
            if (document is null)
            {
                instance = factory?.Invoke();
                
                //
                //
                if (instance is null) return default(T);
                
                //
                //
                _cache[key] = instance;
                
                //
                //
                document = DatabaseBsonMapper.Override
                                     .Serialize(instance)
                                     .AsDocument;
                
                document[Codaria.ID] = keyName;
                
                //
                //
                _objs.Insert(document);
            }
            
            //
            //
            instance = DatabaseBsonMapper.Override.ToObject<T>(document);

            //
            //
            if(cache)_cache[key] = instance;
            
            //
            //
            return instance;
        }
        
        public T Get<T>(bool cache = true) where T : EmbeddedObject
        { 
            var key     = typeof(T);
            var keyName = key.FullName;
            
            if (cache &&  _cache.TryGetValue(key, out var value))
            {
                return (T)value;
            }
            
            var document = _objs.FindById(keyName);
            
            //
            //
            if (document is null)
            {
                return default(T);
            }
            //
            //
            var instance = DatabaseBsonMapper.Override
                                     .ToObject<T>(document);
            //
            //
            _cache[key] = instance;
            //
            //
            return instance;
        }

        public bool Unset<T>(out T value) where T : EmbeddedObject
        {
            var key     = typeof(T);
            var keyName = key.FullName;
            
            if (_cache.Remove(key, out var nonTypeValue))
            {
                //
                // 如果在缓存里，删除缓存以及数据库中的元素
                _objs.Delete(keyName);
                
                //
                // 返回值
                value = nonTypeValue as T;
                return true;
            }

            if (_objs.HasID(keyName))
            {
                _objs.Delete(keyName);
                
                //
                // 返回值
                var document = _objs.FindById(keyName);
            
                //
                //
                if (document is null)
                {
                    value = default(T);
                    return true;
                }
                
                //
                //
                value = DatabaseBsonMapper.Override.ToObject<T>(document);
                return true;
            }

            value = default(T);
            return false;
        }
        
        
        public bool Unset(string keyName) 
        {
            if (_objs.HasID(keyName))
            {
                _objs.Delete(keyName);
                return true;
            }
            
            return false;
        }
        
        public IReadOnlyDictionary<Type, EmbeddedObject> Cache { get; }
    }
}